问题
因为项目需要迁移到8.0平台,发现有一个系统应用打不开,从log发现如下描述:
BroadcastQueue: Background execution not allowed: receiving Intent {...}
实际上,在Android O,像下面的隐式广播都不再起作用:
sendBroadcast(new Intent("this.is.an.implicit.broadcast"));
通常来讲,这个广播会被所有注册这个action的receiver接收到。即便是在Android O版本,还有两类receiver仍然会接收这个广播:
- targetSdkVersion <= 25的应用
- 通过registerReceiver()注册的并有已经启动的进程的应用
但是通过Manifest文件静态注册的BroadcastReceiver是不会接收这个广播的,反而会打印出一些开头提到的log
W/BroadcastQueue: Background execution not allowed: receiving Intent { act=android.intent.action.PACKAGE_REMOVED dat=package:com.commonsware.cwac.cam2.demo flg=0x4000010 (has extras) } to com.commonsware.android.sysevents.pkg/.OnPackageChangeReceiver
症结
这个问题看起来可能与电池有关,因为自从Android 6.0中引入Doze模式以来,各种后台处理会引起类似现象。但事实上,电池问题是次要的,真正的原因是进程混乱。
对此谷歌工程师是这样反馈的:
为了帮助了解发生了什么,我需要澄清一下,此更改的目的并不直接与电池使用有关,而是要解决平台中长期存在的问题:处于内存压力下的设备可能会进入错误的内存抖动状态。这些状态通常是由于广播引起的:某些广播或广播的发送相对频繁,许多应用程序正在通过清单进行监听(因此需要启动以接收它),但是没有足够的RAM来保留所有状态这些应用程序的进程在缓存中进行处理,因此,每次发送广播时,系统最终都会不断地在各个进程中进行跳动。无论设备当前是否已接通电源,这都是一个问题。实际上,这在Android TV设备(始终插上电源)上可能经常会成为问题,因为它们的RAM往往很紧!
这就好理解了,尤其是很多开发者为了唤醒自己的app,注册了非常多的静态广播(我看过喜马拉雅注册了100多个静态广播,毫无下限),每当系统发送一个广播时,就会出现唤醒很多app的情况,又因为系统内存有限,启动一些app后另一些app又被杀掉。这样不仅耗电,还影响使用性能。
Android O 广播限制
如果应用注册了BroadcastReceiver,则每次发送广播的时候,应用的BroadcastReceiver都会消耗资源。如果多个应用注册了接收基于系统事件的广播,就会出现,触发广播的系统事件会导致所有应用快速的连续消耗资源,从而降低用户体验。为了缓解这个问题,Android N对广播施加了一些限制,而Android O让这些限制更加严格。
Android N做的限制
- Android 7.0 及其更高版本不再接收
CONNECTIVITY_ACTION
的静态广播(在清单注册的receiver)。但是如果是通过Context.registerReceiver()
注册的动态广播,相关BroadcastReceiver还是可以接收到CONNECTIVITY_ACTION
广播。 - 应用无法再发送或者接收
ACTION_NEW_PICTURE
或ACTION_NEW_VIDEO
广播了。这项优化影响所有的App,不只是针对Android 7.0。
Android O进一步的限制
在Android 8.0 或更高版本的应用无法继续在其AndroidManifest中为隐式广播注册BroadcastReceiver。 隐式广播是一种不专门针对该应用的广播。 例如,
ACTION_PACKAGE_REPLACED
就是一种隐式广播,因为该广播将被发送给所有已注册侦听器,让后者知道设备上的某些软件包已被替换。 不过,ACTION_MY_PACKAGE_REPLACED
不是隐式广播,因为不管已为该广播注册侦听器的其他应用有多少,它都会只被发送给软件包已被替换的应用。应用可以继续在它们的清单中注册显式广播。
- 应用可以在运行时使用
Context.registerReceiver()
为任意广播(不管是隐式还是显式)注册接收器。 - 需要签名权限的广播不受此限制所限,因为这些广播只会发送到使用相同证书签名的应用,而不是发送到设备上的所有应用。
在许多情况下,之前注册隐式广播的应用使用 JobScheduler
作业可以获得类似的功能。 例如,一款社交照片应用可能需要不时地执行数据清理,并且倾向于在设备连接到充电器时执行此操作。 之前,应用已经在清单中为 ACTION_POWER_CONNECTED
注册了一个接收器;当应用接收到该广播时,它会检查清理是否必要。 为了迁移到 Android 8.0 或更高版本,应用将该接收器从其清单中移除。 应用将清理作业安排在设备处于空闲状态和充电时运行。
例外的隐式广播
很多隐式广播当前已不受此限制所限。 应用可以继续在其清单中为这些广播注册接收器,不管应用适配哪个 API 级别。
注意:即使这些隐式广播仍然可以在后台工作,但你应该尽量避免对它们注册监听。
ACTION_LOCKED_BOOT_COMPLETED,ACTION_BOOT_COMPLETED
因为这些广播只在开机时发送一次,并且很多app需要接收这个广播来安排作业等操作。
ACTION_USER_INITIALIZE,”android.intent.action.USER_ADDED”,”android.intent.action.USER_REMOVED”
这些广播被privileged权限所保护,大多数普通app并收不到这些广播。
“android.intent.action.TIME_SET”, ACTION_TIMEZONE_CHANGED, ACTION_NEXT_ALARM_CLOCK_CHANGED
当时间、时区或闹钟有变化时,时钟应用会需要接收这些广播以更新时钟。
ACTION_LOCALE_CHANGED
这个广播只会在定位变化的时候发送,并不会很频繁。有些应用会需要这些定位变化来更新数据。
ACTION_USB_ACCESSORY_ATTACHED,ACTION_USB_ACCESSORY_DETACHED,ACTION_USB_DEVICE_ATTACHED,ACTION_USB_DEVICE_DETACHED
如果应用程序需要了解这些与USB相关的事件,那么除了注册广播之外,目前没有其他好的选择。
ACTION_CONNECTION_STATE_CHANGED,ACTION_CONNECTION_STATE_CHANGED,ACTION_ACL_CONNECTED,ACTION_ACL_DISCONNECTED
如果应用收到这些蓝牙事件的广播,则用户体验不太可能会受到影响。
ACTION_CARRIER_CONFIG_CHANGED,TelephonyIntents.ACTION_SUBSCRIPTION_CHANGED,TelephonyIntents.SECRET_CODE_ACTION,ACTION_PHONE_STATE_CHANGED,ACTION_PHONE_ACCOUNT_REGISTERED,ACTION_PHONE_ACCOUNT_UNREGISTERED
OEM电话应用程序可能需要接收这些广播。
LOGIN_ACCOUNTS_CHANGED_ACTION
一些应用程序需要了解登录帐户的更改,以便它们可以为新帐户和更改的帐户设置计划的操作。
ACTION_ACCOUNT_REMOVED
拥有帐户可见性的应用在删除帐户后会收到此广播。 如果这是应用程序需要执行的唯一帐户更改,则强烈建议应用程序使用此广播,而不要使用不建议使用的
LOGIN_ACCOUNTS_CHANGED_ACTION
。ACTION_PACKAGE_DATA_CLEARED
仅当用户从“设置”中明确清除其数据时才发送,因此广播接收器不太可能严重影响用户体验。
ACTION_PACKAGE_FULLY_REMOVED
某些应用可能需要在删除另一个软件包后更新其存储的数据; 对于这些应用,没有什么好办法可以注册此广播。
Note: 其他与包相关的广播(例如,ACTION_PACKAGE_REPLACED)不受新限制。 这些广播非常普遍,因此可能会对性能产生影响,因此将其排除在外。
ACTION_NEW_OUTGOING_CALL
响应用户发出呼叫而采取行动的应用需要接收此广播。
ACTION_DEVICE_OWNER_CHANGED
该广播不是经常发送; 一些应用程序需要接收它,以便他们知道设备的安全状态已更改。
ACTION_EVENT_REMINDER
由calendar provider发送,以将事件提醒发布到日历应用程序。 由于日历提供程序不知道日历应用程序是什么,因此此广播必须是隐式的。
ACTION_MEDIA_MOUNTED,ACTION_MEDIA_CHECKING,ACTION_MEDIA_UNMOUNTED,ACTION_MEDIA_EJECT,ACTION_MEDIA_UNMOUNTABLE,ACTION_MEDIA_REMOVED,ACTION_MEDIA_BAD_REMOVAL
这些广播是由于用户与设备的物理交互(安装或删除存储卷)或作为启动初始化的一部分(随着可用卷被挂载)而发送的,因此它们并不常见,通常在用户的控制之下 。
SMS_RECEIVED_ACTION,WAP_PUSH_RECEIVED_ACTION
SMS收件人apps依赖这些广播。
Android O问题的解决方案
检查在应用的清单中定义的BroadcastReceiver。 如果清单里为显式广播声明了接收器,则必须予以替换。 可能的解决方法包括:
通过调用
Context.registerReceiver()
动态创建BroadcastReceiver而不是在清单中声明一个静态的BroadcastReceiver。使用JobSchedular检查条件是否会触发隐式广播。
如果在单个进程中使用广播在应用程序组件之间进行通信,请切换为使用LocalBroadcastManager。
如果使用广播在自己的多个进程中的应用程序组件之间进行通信,请切换为使用显式广播。
如果你想接收系统发送的隐式广播(例如ACTION_PACKAGE_ADDED),建议保持targetSdkVersion为25或更低。
如果非要发送隐式广播,则可以通过查找接收方并发送各个显式广播来突破禁令:
private static void sendImplicitBroadcast(Context ctxt, Intent i) { PackageManager pm=ctxt.getPackageManager(); List
matches=pm.queryBroadcastReceivers(i, 0); for (ResolveInfo resolveInfo : matches) { Intent explicit=new Intent(i); ComponentName cn= new ComponentName(resolveInfo.activityInfo.applicationInfo.packageName, resolveInfo.activityInfo.name); explicit.setComponent(cn); ctxt.sendBroadcast(explicit); } }
官方文档建议:在大多数情况下,应用都可以使用 JobScheduler
克服这些限制。 这种方法允许应用安排其在未活跃运行时执行工作,不过仍能够使系统可以在不影响用户体验的情况下安排这些作业。 Android 8.0 提供针对 JobScheduler
的多项改进,让用户可以更轻松地使用计划作业取代 Service 和BroadcastReceiver: JobScheduler 改进。后面会整理一篇JobScheduler继续跟进这个问题。
参考
https://commonsware.com/blog/2017/04/11/android-o-implicit-broadcast-ban.html